`timescale 1ns/1ns

`define INPUT_SKEW 3ns
`define OUTPUT_SKEW 3ns

// DUT
module dff (
    input  logic        clk,
    input  logic        rstn,
    input  logic [7:0]  d,
    output logic [7:0]  q
);
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            q <= 8'b0;
        end else begin
            q <= d;
        end
    end
endmodule

// Interface
interface dff_if (input logic clk);

    logic       rstn;
    logic [7:0] d;
    logic [7:0] q;

    clocking cb @(posedge clk);
        default input #`INPUT_SKEW output #`OUTPUT_SKEW;
        // 从 TB 视角看, 在时钟上升沿前 `INPUT_SKEW ns 采样数据
        // 在时钟上升沿后 `OUTPUT_SKEW ns 驱动数据
        output d, rstn; // to DUT
        input  q;       // from DUT

    endclocking

    modport dut (
        input clk,
        input rstn,
        input d,
        output q
    );

    modport tb (clocking cb);

endinterface


module clocking_block;
    logic [7:0] next_d;
    // 时钟信号
    logic clk;
    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end

    // 实例化接口
    dff_if dif(clk);

    // 实例化 DFF, 将接口连接到 DUT
    dff u_dut (
        .clk    (dif.clk),
        .rstn  (dif.rstn),
        .d      (dif.d),
        .q      (dif.q)
    );

    // 驱动信号
    initial begin
        // 初始化信号
        dif.rstn = 0;
        dif.d = 0;

        // 复位
        #3  dif.rstn = 0;
        #10 dif.rstn = 1;

        // 驱动数据
        repeat (10) begin
            // 在时钟上升沿处将随机值赋给 dif.cb.d, 然后等待 `OUTPUT_SKEW ns 将其驱动到 dif.d
            // next_d ---------> dif.cb.d -[delay]-> dif.d
            @(dif.cb) next_d = $urandom_range(0, 255);
            dif.cb.d <= next_d; // 注意: 这里 dif.cb.d 与普通的寄存器不一样, dif.cb.d 会立刻更新新的值
            $display("@(dif.cb)\t[%0tns]\tnext_d   is %0h", $time, next_d);
            // VCS: Sampling of clocking block output signal is not allowed.
            // $display("@(dif.cb)\t[%0tns]\tdif.cb.d is %0h", $time, dif.cb.d); // 输出同 next_d
            $display("@(dif.cb)\t[%0tns]\tdif.d    is %0h", $time, dif.d); // 采集到未变化的旧值
        end

        $finish;
    end
    
    // 采样数据
    initial begin
        forever begin
            // 在时钟上升沿后 `OUTPUT_SKEW ns, dif.cb.d 驱动到 dif.d
            // next_d ---------> dif.cb.d -[delay]-> dif.d
            @(dif.d) $display("@(dif.d)\t[%0tns]\tD is driven to %0h", $time, dif.d);
        end
    end
    initial begin
        forever begin
            // 在时钟上升沿处停下, 仿真器"回顾"此前 `INPUT_SKEW ns q 的值, 将其采样到 dif.cb.q
            @(dif.cb); // @(dif.cb) 事件在 Observed 区域触发, 此时 dif.q 的值已经更新
            $display("@(dif.cb)\t[%0tns]\tdif.cb.q is %0h", $time, dif.cb.q);
            $display("@(dif.cb)\t[%0tns]\tdif.q    is %0h", $time, dif.q);
        end
    end
    initial begin
        forever begin
            @(dif.q) $display("@(dif.q)\t[%0tns]\tdif.q    is %0h", $time, dif.q);
        end
    end
    /*
    Upon processing its specified clocking event, a clocking block shall trigger the event associated with the clocking block name. 
    This event shall be triggered in the Observed region and is referred to as a clocking block event.
    */
    initial begin
        forever begin
            @(dif.cb.q) $display("@(dif.cb.q)\t[%0tns]\tdif.cb.q is %0h", $time, dif.cb.q);
        end
    end

    initial begin
        forever begin
            @(posedge clk); // @(posedge clk) 事件在 Active 区域触发, 此时 dif.q 的值还未更新
            $display("At time %0t: dif.d = %0h, dif.q = %0h", $time, dif.d, dif.q);
        end
    end

    initial begin
        $dumpfile("waves.vcd");
        $dumpvars(0, clocking_block);
    end

endmodule

/* Output in QuestaSim:
# @(dif.q)	[0ns]	dif.q    is 0
# At time 5: dif.d = 0, dif.q = 0
# @(dif.cb.q)	[5ns]	dif.cb.q is 0
# @(dif.cb)	[5ns]	dif.cb.q is 0
# @(dif.cb)	[5ns]	dif.q    is 0
# At time 15: dif.d = 0, dif.q = 0
# @(dif.cb)	[15ns]	next_d   is e7
# @(dif.cb)	[15ns]	dif.d    is 0
# @(dif.cb)	[15ns]	dif.cb.q is 0
# @(dif.cb)	[15ns]	dif.q    is 0
# @(dif.d)	[18ns]	D is driven to e7
# At time 25: dif.d = e7, dif.q = 0
# @(dif.q)	[25ns]	dif.q    is e7
# @(dif.cb)	[25ns]	next_d   is 7c
# @(dif.cb)	[25ns]	dif.d    is e7
# @(dif.cb)	[25ns]	dif.cb.q is 0
# @(dif.cb)	[25ns]	dif.q    is e7
# @(dif.d)	[28ns]	D is driven to 7c
# At time 35: dif.d = 7c, dif.q = e7
# @(dif.q)	[35ns]	dif.q    is 7c
# @(dif.cb.q)	[35ns]	dif.cb.q is e7
# @(dif.cb)	[35ns]	next_d   is 9c
# @(dif.cb)	[35ns]	dif.d    is 7c
# @(dif.cb)	[35ns]	dif.cb.q is e7
# @(dif.cb)	[35ns]	dif.q    is 7c
# @(dif.d)	[38ns]	D is driven to 9c
# At time 45: dif.d = 9c, dif.q = 7c
# @(dif.q)	[45ns]	dif.q    is 9c
# @(dif.cb.q)	[45ns]	dif.cb.q is 7c
# @(dif.cb)	[45ns]	next_d   is 1c
# @(dif.cb)	[45ns]	dif.d    is 9c
# @(dif.cb)	[45ns]	dif.cb.q is 7c
# @(dif.cb)	[45ns]	dif.q    is 9c
# @(dif.d)	[48ns]	D is driven to 1c
# At time 55: dif.d = 1c, dif.q = 9c
# @(dif.q)	[55ns]	dif.q    is 1c
# @(dif.cb.q)	[55ns]	dif.cb.q is 9c
# @(dif.cb)	[55ns]	next_d   is e8
# @(dif.cb)	[55ns]	dif.d    is 1c
# @(dif.cb)	[55ns]	dif.cb.q is 9c
# @(dif.cb)	[55ns]	dif.q    is 1c
# @(dif.d)	[58ns]	D is driven to e8
# At time 65: dif.d = e8, dif.q = 1c
# @(dif.q)	[65ns]	dif.q    is e8
# @(dif.cb.q)	[65ns]	dif.cb.q is 1c
# @(dif.cb)	[65ns]	next_d   is a7
# @(dif.cb)	[65ns]	dif.d    is e8
# @(dif.cb)	[65ns]	dif.cb.q is 1c
# @(dif.cb)	[65ns]	dif.q    is e8
# @(dif.d)	[68ns]	D is driven to a7
# At time 75: dif.d = a7, dif.q = e8
# @(dif.q)	[75ns]	dif.q    is a7
# @(dif.cb.q)	[75ns]	dif.cb.q is e8
# @(dif.cb)	[75ns]	next_d   is 16
# @(dif.cb)	[75ns]	dif.d    is a7
# @(dif.cb)	[75ns]	dif.cb.q is e8
# @(dif.cb)	[75ns]	dif.q    is a7
# @(dif.d)	[78ns]	D is driven to 16
# At time 85: dif.d = 16, dif.q = a7
# @(dif.q)	[85ns]	dif.q    is 16
# @(dif.cb.q)	[85ns]	dif.cb.q is a7
# @(dif.cb)	[85ns]	next_d   is c4
# @(dif.cb)	[85ns]	dif.d    is 16
# @(dif.cb)	[85ns]	dif.cb.q is a7
# @(dif.cb)	[85ns]	dif.q    is 16
# @(dif.d)	[88ns]	D is driven to c4
# At time 95: dif.d = c4, dif.q = 16
# @(dif.q)	[95ns]	dif.q    is c4
# @(dif.cb.q)	[95ns]	dif.cb.q is 16
# @(dif.cb)	[95ns]	next_d   is 0
# @(dif.cb)	[95ns]	dif.d    is c4
# @(dif.cb)	[95ns]	dif.cb.q is 16
# @(dif.cb)	[95ns]	dif.q    is c4
# @(dif.d)	[98ns]	D is driven to 0
# At time 105: dif.d = 0, dif.q = c4
# @(dif.q)	[105ns]	dif.q    is 0
# @(dif.cb.q)	[105ns]	dif.cb.q is c4
# @(dif.cb)	[105ns]	next_d   is 59
# @(dif.cb)	[105ns]	dif.d    is 0
*/

/* Output in Vivado:
@(dif.q)        [0ns]   dif.q    is 0
At time 5: dif.d = 0, dif.q = 0
@(dif.cb.q)     [5ns]   dif.cb.q is 0
@(dif.cb)       [5ns]   dif.cb.q is 0
@(dif.cb)       [5ns]   dif.q    is 0
At time 15: dif.d = 0, dif.q = 0
@(dif.cb)       [15ns]  next_d   is 10
@(dif.cb)       [15ns]  dif.d    is 0
@(dif.cb)       [15ns]  dif.cb.q is 0
@(dif.cb)       [15ns]  dif.q    is 0
@(dif.d)        [18ns]  D is driven to 10
At time 25: dif.d = 10, dif.q = 0
@(dif.cb)       [25ns]  next_d   is 6a
@(dif.cb)       [25ns]  dif.d    is 10
@(dif.cb)       [25ns]  dif.cb.q is 0
@(dif.cb)       [25ns]  dif.q    is 0
@(dif.q)        [25ns]  dif.q    is 10
@(dif.d)        [28ns]  D is driven to 6a
At time 35: dif.d = 6a, dif.q = 10
@(dif.cb.q)     [35ns]  dif.cb.q is 10
@(dif.cb)       [35ns]  next_d   is a8
@(dif.cb)       [35ns]  dif.d    is 6a
@(dif.cb)       [35ns]  dif.cb.q is 10
@(dif.cb)       [35ns]  dif.q    is 10
@(dif.q)        [35ns]  dif.q    is 6a
@(dif.d)        [38ns]  D is driven to a8
At time 45: dif.d = a8, dif.q = 6a
@(dif.cb.q)     [45ns]  dif.cb.q is 6a
@(dif.cb)       [45ns]  next_d   is 2b
@(dif.cb)       [45ns]  dif.d    is a8
@(dif.cb)       [45ns]  dif.cb.q is 6a
@(dif.cb)       [45ns]  dif.q    is 6a
@(dif.q)        [45ns]  dif.q    is a8
@(dif.d)        [48ns]  D is driven to 2b
At time 55: dif.d = 2b, dif.q = a8
@(dif.cb.q)     [55ns]  dif.cb.q is a8
@(dif.cb)       [55ns]  next_d   is bf
@(dif.cb)       [55ns]  dif.d    is 2b
@(dif.cb)       [55ns]  dif.cb.q is a8
@(dif.cb)       [55ns]  dif.q    is a8
@(dif.q)        [55ns]  dif.q    is 2b
@(dif.d)        [58ns]  D is driven to bf
At time 65: dif.d = bf, dif.q = 2b
@(dif.cb.q)     [65ns]  dif.cb.q is 2b
@(dif.cb)       [65ns]  next_d   is a1
@(dif.cb)       [65ns]  dif.d    is bf
@(dif.cb)       [65ns]  dif.cb.q is 2b
@(dif.cb)       [65ns]  dif.q    is 2b
@(dif.q)        [65ns]  dif.q    is bf
@(dif.d)        [68ns]  D is driven to a1
At time 75: dif.d = a1, dif.q = bf
@(dif.cb.q)     [75ns]  dif.cb.q is bf
@(dif.cb)       [75ns]  next_d   is 7e
@(dif.cb)       [75ns]  dif.d    is a1
@(dif.cb)       [75ns]  dif.cb.q is bf
@(dif.cb)       [75ns]  dif.q    is bf
@(dif.q)        [75ns]  dif.q    is a1
@(dif.d)        [78ns]  D is driven to 7e
At time 85: dif.d = 7e, dif.q = a1
@(dif.cb.q)     [85ns]  dif.cb.q is a1
@(dif.cb)       [85ns]  next_d   is cf
@(dif.cb)       [85ns]  dif.d    is 7e
@(dif.cb)       [85ns]  dif.cb.q is a1
@(dif.cb)       [85ns]  dif.q    is a1
@(dif.q)        [85ns]  dif.q    is 7e
@(dif.d)        [88ns]  D is driven to cf
At time 95: dif.d = cf, dif.q = 7e
@(dif.cb.q)     [95ns]  dif.cb.q is 7e
@(dif.cb)       [95ns]  next_d   is b
@(dif.cb)       [95ns]  dif.d    is cf
@(dif.cb)       [95ns]  dif.cb.q is 7e
@(dif.cb)       [95ns]  dif.q    is 7e
@(dif.q)        [95ns]  dif.q    is cf
@(dif.d)        [98ns]  D is driven to b
At time 105: dif.d = b, dif.q = cf
@(dif.cb.q)     [105ns] dif.cb.q is cf
@(dif.cb)       [105ns] next_d   is 26
@(dif.cb)       [105ns] dif.d    is b
*/

/* Output in VCS:
@(dif.d)	[0ns]	D is driven to 0
@(dif.q)	[0ns]	dif.q    is 0
At time 5: dif.d = 0, dif.q = 0
@(dif.cb.q)	[5ns]	dif.cb.q is 0
@(dif.cb)	[5ns]	dif.cb.q is 0
@(dif.cb)	[5ns]	dif.q    is 0
At time 15: dif.d = 0, dif.q = 0
@(dif.cb)	[15ns]	dif.cb.q is 0
@(dif.cb)	[15ns]	dif.q    is 0
@(dif.cb)	[15ns]	next_d   is 13
@(dif.cb)	[15ns]	dif.d    is 0
@(dif.d)	[18ns]	D is driven to 13
At time 25: dif.d = 13, dif.q = 0
@(dif.q)	[25ns]	dif.q    is 13
@(dif.cb)	[25ns]	dif.cb.q is 0
@(dif.cb)	[25ns]	dif.q    is 13
@(dif.cb)	[25ns]	next_d   is 70
@(dif.cb)	[25ns]	dif.d    is 13
@(dif.d)	[28ns]	D is driven to 70
At time 35: dif.d = 70, dif.q = 13
@(dif.cb.q)	[35ns]	dif.cb.q is 13
@(dif.q)	[35ns]	dif.q    is 70
@(dif.cb)	[35ns]	dif.cb.q is 13
@(dif.cb)	[35ns]	dif.q    is 70
@(dif.cb)	[35ns]	next_d   is fd
@(dif.cb)	[35ns]	dif.d    is 70
@(dif.d)	[38ns]	D is driven to fd
At time 45: dif.d = fd, dif.q = 70
@(dif.cb.q)	[45ns]	dif.cb.q is 70
@(dif.q)	[45ns]	dif.q    is fd
@(dif.cb)	[45ns]	dif.cb.q is 70
@(dif.cb)	[45ns]	dif.q    is fd
@(dif.cb)	[45ns]	next_d   is e2
@(dif.cb)	[45ns]	dif.d    is fd
@(dif.d)	[48ns]	D is driven to e2
At time 55: dif.d = e2, dif.q = fd
@(dif.cb.q)	[55ns]	dif.cb.q is fd
@(dif.q)	[55ns]	dif.q    is e2
@(dif.cb)	[55ns]	dif.cb.q is fd
@(dif.cb)	[55ns]	dif.q    is e2
@(dif.cb)	[55ns]	next_d   is 97
@(dif.cb)	[55ns]	dif.d    is e2
@(dif.d)	[58ns]	D is driven to 97
At time 65: dif.d = 97, dif.q = e2
@(dif.cb.q)	[65ns]	dif.cb.q is e2
@(dif.q)	[65ns]	dif.q    is 97
@(dif.cb)	[65ns]	dif.cb.q is e2
@(dif.cb)	[65ns]	dif.q    is 97
@(dif.cb)	[65ns]	next_d   is f1
@(dif.cb)	[65ns]	dif.d    is 97
@(dif.d)	[68ns]	D is driven to f1
At time 75: dif.d = f1, dif.q = 97
@(dif.cb.q)	[75ns]	dif.cb.q is 97
@(dif.q)	[75ns]	dif.q    is f1
@(dif.cb)	[75ns]	dif.cb.q is 97
@(dif.cb)	[75ns]	dif.q    is f1
@(dif.cb)	[75ns]	next_d   is c5
@(dif.cb)	[75ns]	dif.d    is f1
@(dif.d)	[78ns]	D is driven to c5
At time 85: dif.d = c5, dif.q = f1
@(dif.cb.q)	[85ns]	dif.cb.q is f1
@(dif.q)	[85ns]	dif.q    is c5
@(dif.cb)	[85ns]	dif.cb.q is f1
@(dif.cb)	[85ns]	dif.q    is c5
@(dif.cb)	[85ns]	next_d   is ec
@(dif.cb)	[85ns]	dif.d    is c5
@(dif.d)	[88ns]	D is driven to ec
At time 95: dif.d = ec, dif.q = c5
@(dif.cb.q)	[95ns]	dif.cb.q is c5
@(dif.q)	[95ns]	dif.q    is ec
@(dif.cb)	[95ns]	dif.cb.q is c5
@(dif.cb)	[95ns]	dif.q    is ec
@(dif.cb)	[95ns]	next_d   is 48
@(dif.cb)	[95ns]	dif.d    is ec
@(dif.d)	[98ns]	D is driven to 48
At time 105: dif.d = 48, dif.q = ec
@(dif.cb.q)	[105ns]	dif.cb.q is ec
@(dif.q)	[105ns]	dif.q    is 48
@(dif.cb)	[105ns]	dif.cb.q is ec
@(dif.cb)	[105ns]	dif.q    is 48
@(dif.cb)	[105ns]	next_d   is c
@(dif.cb)	[105ns]	dif.d    is 48
*/